package kz.kstu.aecdemo.business.server.impl;

import kz.kstu.aec.Authenticator;
import kz.kstu.aec.model.AuthenticationData;
import kz.kstu.aec.model.Point2D;
import kz.kstu.aec.model.ServerData;
import kz.kstu.aecdemo.business.common.exception.LoginFailedException;
import kz.kstu.aecdemo.business.entity.CommonEntityConstant;
import kz.kstu.aecdemo.business.entity.security.AbstractAccount;
import kz.kstu.aecdemo.business.entity.security.ECAccount;
import kz.kstu.aecdemo.business.server.api.AccountService;

import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.security.auth.login.AccountException;
import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.LoginException;
import java.math.BigInteger;
import java.util.List;


/**
 * @author Filipp_Stankevich
 *         created: 08.09.11 15:42
 */
public class AccountServiceImpl extends AbstractService implements AccountService {

    public static final int AUTHENTICATION_NUMBER_LENGTH = 256;

    @Override
    public void register(String login, Point2D publicKey) throws AccountException {
        Query query = getEntityManager().createNamedQuery(CommonEntityConstant.FIND_ACCOUNT_BY_LOGIN);
        query.setParameter("login", login);
        List<AbstractAccount> accounts = query.getResultList();

        if (accounts.size() > 0) {
            throw  new AccountException("Login already exists");
        }

        ECAccount account = new ECAccount();
        account.setLogin(login);
        account.setX(publicKey.getX());
        account.setY(publicKey.getY());
        getEntityManager().persist(account);
    }

    public BigInteger getAuthenticationNumber(String login) throws AccountNotFoundException {
        Query query = getEntityManager().createNamedQuery(CommonEntityConstant.FIND_EC_ACCOUNT_BY_LOGIN);
        query.setParameter("login", login);
        ECAccount account;
        try {
            account = (ECAccount) query.getSingleResult();
        } catch (NoResultException e) {
           throw new AccountNotFoundException("Account with login = " + login + "not found");
        }
        Authenticator authenticator = Authenticator.getInstance();
        BigInteger authenticationNumber = authenticator.generateAuthenticationNumber();
        account.setAuthenticationNumber(authenticationNumber);
        getEntityManager().merge(account);
        return authenticationNumber;
    }

    @Override
    public ECAccount login(AuthenticationData authenticationData) throws LoginException {
        Query query = getEntityManager().createNamedQuery(CommonEntityConstant.FIND_EC_ACCOUNT_BY_LOGIN);
        String login = authenticationData.getLogin();
        query.setParameter("login", login);
        ECAccount account;
        try {
            account = (ECAccount) query.getSingleResult();
        } catch (NoResultException e) {
           throw new AccountNotFoundException("Account with login = " + login + "not found");
        }
        Point2D publicPoint = new Point2D(account.getX(), account.getY());
        ServerData serverData = new ServerData(publicPoint, account.getAuthenticationNumber());
        Authenticator authenticator = Authenticator.getInstance();
        boolean isValid = authenticator.checkAuthenticationData(authenticationData, serverData);
        if (isValid) {
            return account;
        } else {
            throw new LoginFailedException("Check authentication data filed");
        }
    }

    @Override
    public void changePassword(String login, Point2D publicKey) throws AccountNotFoundException {
        Query query = getEntityManager().createNamedQuery(CommonEntityConstant.FIND_ACCOUNT_BY_LOGIN);
        query.setParameter("login", login);
        ECAccount account;
        try {
            account = (ECAccount) query.getSingleResult();
        } catch (NoResultException e) {
           throw new AccountNotFoundException("Account with login = " + login + "not found");
        }
        account.setLogin(login);
        account.setX(publicKey.getX());
        account.setY(publicKey.getY());
        getEntityManager().persist(account);
    }

    public int getUserCount() {
        Query query = getEntityManager().createNamedQuery(CommonEntityConstant.FIND_ALL_ACCOUNTS);
        List<AbstractAccount> accounts = query.getResultList();
        return accounts.size();
    }
}
